package dataTypes;

import helperClasses.Help;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * 
 * @author Felix Schliephacke
 * 
 */
public final class SudokuImpl implements Sudoku<Integer> {

	public static Sudoku<Integer> getInstance(final int size)
			throws IllegalArgumentException {
		return new SudokuImpl(size);
	}

	public static Sudoku<Integer> getInstance(final Integer[][] field)
			throws IllegalArgumentException {
		return new SudokuImpl(field);
	}

	public static Sudoku<Integer> getInstance(final Sudoku<Integer> sudoku) {
		return new SudokuImpl(sudoku);
	}

	private transient final Field<Integer>[][] fields;
	private transient final Field<Integer> possibleContent;
	private transient final int size;
	private transient final int squareSize;

	private SudokuImpl(final int size) throws IllegalArgumentException {
		if (size <= 0) {
			throw new IllegalArgumentException(
					"Argument must be greater than 0.");
		}
		this.squareSize = size;
		this.size = this.squareSize * this.squareSize;
		this.possibleContent = FieldImpl.getInstance(this.squareSize);
		this.fields = new FieldImpl[this.size][this.size];
		for (int row = 0; row < this.size; row++) {
			for (int columnj = 0; columnj < this.size; columnj++) {
				this.fields[row][columnj] = FieldImpl
						.getInstance(this.squareSize);
			}
		}
	}

	private SudokuImpl(final Integer[][] field) throws IllegalArgumentException {
		if (!Help.isPositiveSquare(field.length)) {
			throw new IllegalArgumentException("Field length must be a square.");
		}
		this.size = field.length;
		this.squareSize = (int) Math.sqrt(this.size);
		this.possibleContent = FieldImpl.getInstance(this.size);
		this.fields = new FieldImpl[this.size][this.size];
		for (int i = 0; i < this.size; i++) {
			if (field[i].length != this.size) {
				throw new IllegalArgumentException("Dimension mismatch. Row "
						+ i + " was of size " + field[i].length
						+ " but all rows have to be of size " + this.size + ".");
			}
			for (int j = 0; j < this.size; j++) {
				this.fields[i][j] = FieldImpl.getInstance(this.squareSize,
						field[i][j]);
			}
		}
	}

	private SudokuImpl(final Sudoku<Integer> sudoku) {
		this.size = sudoku.getSize();
		this.squareSize = sudoku.getSquareSize();
		this.possibleContent = FieldImpl.getInstance(this.size);
		this.fields = new FieldImpl[this.size][this.size];
		for (int i = 0; i < this.size; i++) {
			for (int j = 0; j < this.size; j++) {
				this.fields[i][j] = sudoku.getField(i, j).clone();
			}
		}
	}

	@Override
	public Sudoku<Integer> clone() {
		return SudokuImpl.getInstance(this);
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof SudokuImpl)) {
			return false;
		}
		final SudokuImpl other = (SudokuImpl) obj;
		for (int i = 0; i < this.size; i++) {
			for (int j = 0; j < this.size; j++) {
				if (!this.fields[i][j].equals(other.fields[i][j])) {
					return false;
				}
			}
		}
		if (this.possibleContent == null) {
			if (other.possibleContent != null) {
				return false;
			}
		} else if (!this.possibleContent.equals(other.possibleContent)) {
			return false;
		}
		if (this.size != other.size) {
			return false;
		}
		if (this.squareSize != other.squareSize) {
			return false;
		}
		return true;
	}

	@Override
	public Field<Integer>[] getColumn(final int column)
			throws IllegalArgumentException {
		if (column >= this.size) {
			throw new IllegalArgumentException("Column index must be below "
					+ this.size + ".");
		}
		final Field<Integer>[] temp = new FieldImpl[this.size];
		for (int i = 0; i < this.size; i++) {
			temp[i] = this.getField(i, column);
		}
		return temp;
	}

	@Override
	public Field<Integer> getField(final int row, final int column)
			throws IllegalArgumentException {
		if (row >= this.size) {
			throw new IllegalArgumentException("Row index must be below "
					+ this.size + ".");
		}
		if (column >= this.size) {
			throw new IllegalArgumentException("Column index must be below "
					+ this.size + ".");
		}
		return this.fields[row][column];// .clone(); TODO
	}

	@Override
	public Field<Integer> getPossibleContent() {
		return this.possibleContent;
		// return possibleContent.clone();
	}

	@Override
	public Field<Integer>[] getRow(final int row)
			throws IllegalArgumentException {
		if (row >= this.size) {
			throw new IllegalArgumentException("Row index must be below "
					+ this.size + ".");
		}
		final Field<Integer>[] temp = new FieldImpl[this.size];
		for (int i = 0; i < this.size; i++) {
			temp[i] = this.getField(row, i);
		}
		return temp;
	}

	@Override
	public int getSize() {
		return this.size;
	}

	@Override
	public Field<Integer>[] getSquare(final int squareIndex)
			throws IllegalArgumentException {
		if (squareIndex >= this.size) {
			throw new IllegalArgumentException("Square index must be below "
					+ this.size + ".");
		}
		final Field<Integer>[] square = new FieldImpl[this.size];
		final int[] rowIndices = Help.calculateRowIndices(squareIndex,
				this.squareSize);
		final int[] columnIndices = Help.calculateColumnIndices(squareIndex,
				this.squareSize);
		for (int i = 0, arrayIndex = 0; i < this.squareSize; i++) {
			for (int j = 0; j < this.squareSize; j++, arrayIndex++) {
				square[arrayIndex] = this.getField(rowIndices[i],
						columnIndices[j]);
			}
		}
		return square;
	}

	@Override
	public int getSquareSize() {
		return this.squareSize;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(this.fields);
		result = prime
				* result
				+ (this.possibleContent == null ? 0 : this.possibleContent
						.hashCode());
		result = prime * result + this.size;
		result = prime * result + this.squareSize;
		return result;
	}

	@Override
	public boolean isSolvable() {
		for (int row = 0; row < this.size; row++) {
			for (int column = 0; column < this.size; column++) {
				if (this.getField(row, column).size() == 0) {
					return false;
				}
			}
		}
		return true;
	}

	@Override
	public boolean isSolved() {
		for (int i = 0; i < this.getSize(); i++) {
			for (int j = 0; j < this.getSize(); j++) {
				if (this.getField(i, j).size() != 1) {
					return false;
				}
			}
		}
		/*
		 * TODO Performance test
		 */
		return this.isValidSolution();
		// return true;
	}

	private boolean isValidSolution() {
		Field<Integer>[] row;
		Field<Integer>[] column;
		Field<Integer>[] square;
		for (int i = 0; i < this.getSize(); i++) {
			row = this.getRow(i);
			// final List<Integer> rowCounter = new ArrayList<Integer>();
			final List<Integer> counter = new LinkedList<Integer>();
			for (final Field<Integer> field : row) {
				if (counter.contains(field.get(0))) {
					return false;
				} else {
					counter.add(field.get(0));
				}
			}
			column = this.getColumn(i);
			// final List<Integer> columnCounter = new ArrayList<Integer>();
			counter.clear();
			for (final Field<Integer> field : column) {
				if (counter.contains(field.get(0))) {
					return false;
				} else {
					counter.add(field.get(0));
				}
			}
			square = this.getSquare(i);
			// final List<Integer> squareCounter = new ArrayList<Integer>();
			counter.clear();
			for (final Field<Integer> field : square) {
				if (counter.contains(field.get(0))) {
					return false;
				} else {
					counter.add(field.get(0));
				}
			}
		}
		return true;
	}

	private boolean removeFromColumn(final int row, final int column,
			final Integer toRemove) {
		boolean changed = false;
		for (int i = 0; i < this.size; i++) {
			if (i != row && this.removeFromField(i, column, toRemove)) {
				changed = true;
			}
		}
		return changed;
	}

	@Override
	public boolean removeFromContext(final int row, final int column,
			final Integer toRemove) {
		return this.removeFromRow(row, column, toRemove)
				| this.removeFromColumn(row, column, toRemove)
				| this.removeFromSquare(row, column, toRemove);
	}

	@Override
	public boolean removeFromField(final int row, final int column,
			final Integer arg) throws IllegalArgumentException {
		return this.fields[row][column].remove(arg);
	}

	private boolean removeFromRow(final int row, final int column,
			final Integer toRemove) {
		boolean changed = false;
		for (int i = 0; i < this.size; i++) {
			if (i != column && this.removeFromField(row, i, toRemove)) {
				changed = true;
			}
		}
		return changed;
	}

	private boolean removeFromSquare(final int row, final int column,
			final Integer toRemove) {
		boolean changed = false;
		final int squareIndex = Help.calculateSquareIndex(row, column,
				this.squareSize);
		final int[] rowIndices = Help.calculateRowIndices(squareIndex,
				this.squareSize);
		final int[] columnIndices = Help.calculateColumnIndices(squareIndex,
				this.squareSize);
		for (int i = 0; i < this.squareSize; i++) {
			final int tempRow = rowIndices[i];
			for (int j = 0; j < this.squareSize; j++) {
				final int tempColumn = columnIndices[j];
				if ((tempRow != row || tempColumn != column)
						&& this.removeFromField(tempRow, tempColumn, toRemove)) {
					changed = true;
				}
			}
		}
		return changed;
	}

	@Override
	public void reset() {
		for (int row = 0; row < this.size; row++) {
			for (int column = 0; column < this.size; column++) {
				this.resetField(row, column);
			}
		}
	}

	@Override
	public void resetColumn(final int column) throws IllegalArgumentException {
		for (int row = 0; row < this.size; row++) {
			this.resetField(row, column);
		}
	}

	@Override
	public void resetField(final int row, final int column)
			throws IllegalArgumentException {
		this.fields[row][column].reset();
	}

	@Override
	public void resetRow(final int row) throws IllegalArgumentException {
		for (int column = 0; column < this.size; column++) {
			this.resetField(row, column);
		}
	}

	@Override
	public void resetSquare(final int square) {
		final int[] rows = Help.calculateRowIndices(square, this.squareSize);
		final int[] columns = Help.calculateColumnIndices(square,
				this.squareSize);
		for (int i = 0; i < this.squareSize; i++) {
			for (int j = 0; j < this.squareSize; j++) {
				this.resetField(rows[i], columns[j]);
			}
		}
	}

	@Override
	public void setField(final int row, final int column, final Integer content) {
		this.fields[row][column].set(content);
	}

	@Override
	public String toString() {
		final StringBuffer temp = new StringBuffer();
		for (int i = 0; i < this.size; i++) {
			for (int j = 0; j < this.size; j++) {
				temp.append(this.fields[i][j].toString());
				for (int k = 0; k <= this.size - this.fields[i][j].size(); k++) {
					temp.append("   ");
				}
			}
			temp.append("\n");
		}
		return temp.toString();
	}

	// @Override
	// protected void finalize() {
	// possibleContent = null;
	// fields = null;
	// }
}
